__license__ = "LGPL <http://creativecommons.org/licenses/LGPL/2.1/>"
__author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>"

import re
import os.path
import sys
import stat
import gobject
import gtk
import thread

from rat import hig

file_size = lambda filename: os.stat(filename)[stat.ST_SIZE]

gobject.threads_init()

def _strip_next_line(buff):
    # skip first line
    line = buff.readline()
    line = line.strip()
    while line == "" or line.startswith("REM "):
        line = buff.readline()
        line = line.strip()
        

    return line

def parse_cue(buff):
    """
    @return: filename, mode, frame_size
    """
    filename_re = re.compile(r'\s*FILE\s+"(.*)"\s+BINARY\s*')
    track_mode_re = re.compile(r'\s*TRACK\s+[0-9]+\s+MODE([0-9])/([0-9]+)\s*')
    
    line = _strip_next_line(buff)
    match = filename_re.match(line)
    if match is None:
        raise StandardError("File format unknown")

    filename = match.group(1)

    line = _strip_next_line(buff)
    match = track_mode_re.match(line)
    if match is None:
        raise StandardError("File format unknown")

    mode = int(match.group(1))
    frame = int(match.group(2))
    return filename, mode, frame
    


_modes = {
    #(mode, in_size): (offset, out_size)
    (1, 2352): (16, 2048),
    (2, 2336): (16, 2336),
    (2, 2352): (24, 2048),
}

class ReadChunk:
    """
    This class contains a file descripter C{ReadChunk.buffer} which must be closed
    after the data read through.
    """
    def __init__(self, filename, buffer, bytes_in, bytes_out, offset):
        self.bytes_in = bytes_in
        self.bytes_out = bytes_out
        self.offset = offset
        self.buffer = buffer
        self.filename = filename

    def __call__(self):
        data = self.buffer.read(self.bytes_in)[self.offset:][:self.bytes_out]
        assert data == "" or len(data) == self.bytes_out
        return data
        
def extract_user_data(filename):
    filename = os.path.abspath(filename)
    basedir = os.path.dirname(filename)
    
    cue_buff = open(filename)
    try:
        bin_filename, mode, frames = parse_cue(cue_buff)
    finally:
        cue_buff.close()
    
    offset, out_frames = _modes[(mode, frames)]
    
    if not os.path.isabs(bin_filename):
        bin_filename = os.path.join(basedir, bin_filename)
        if not os.path.exists(bin_filename):
            bin_filename = os.path.abspath(bin_filename)
    
    return ReadChunk(bin_filename, open(bin_filename), frames, out_frames, offset)

class ReaderThread:
    def __init__(self, reader, writer, fraction, on_finished):
        self.reader = reader
        self.writer = writer
        self.fraction = fraction
        self.on_finished = on_finished
        self.written = 0.0
        
    def __call__(self):
        data = self.reader()
        while data != "":
            self.writer(data)
            self.written += self.fraction

            data = self.reader()

        self.on_finished()

class Cue2IsoGui:
    def __init__(self):
        progress = hig.HigProgress()
        progress.set_title("Converting CUE/BIN to ISO")
        progress.primary_text = "Converting CUE/BIN to ISO"
        self.progress = progress
    
    def __call__(self, filename):
        if len(filename.rsplit(".", 1)) == 2:
            target_filename = filename.rsplit(".", 1)[0]
        else:
            target_filename = filename
        
        target_filename += ".iso"
        self.fd = open(target_filename, "w")
        
        self.progress.secondary_text = "Converting <i>%s</i> to <i>%s</i>" % (
            filename,
            target_filename
        )
        reader = extract_user_data(filename)
        self.total = float(file_size(reader.filename))
        
        self.reader = ReaderThread(
            reader=reader,
            writer=self.fd.write,
            fraction=reader.bytes_in,
            on_finished=lambda: gobject.idle_add(self.on_finished)
        )
        
        thread.start_new_thread(self.reader, ())
            
        gobject.timeout_add(200, self.on_tick)
        self.progress.show()
    
    def on_finished(self):
        self.fd.close()
        hig.dialog_info("Convertion finished", "The ISO image was created successfully.", parent=self.progress)
        gtk.main_quit()
        
    def on_tick(self):
        self.progress.progress_fraction = self.reader.written / self.total
        return True
    
def test_readbuffer():
    from StringIO import StringIO
    buff = StringIO(("X" * 10 + "U" * 4 + "Y" * 7) * 4)
    reader = _ReadChunk(buff, 10 + 4 + 7, 4, 10)
    data = ""
    chunk = reader()
    
    while chunk != "":
        data += chunk
        chunk = reader()
    
    assert data == "U" * 4 * 4

def main():
    if len(sys.argv) != 2:
        print >> sys.stderr, "Usage: %s <filename.cue>"
        return
        
    cue2iso = Cue2IsoGui()
    cue2iso(sys.argv[1])
    gtk.main()

if __name__ == '__main__':
    main()
